local ROT = require 'rot'
local RNG = ROT.RNG

local Builder = ROT.Class:extend()

local System = require 'game.system'
local Light = require 'game.light'

local Feature = require 'game.proto.feature'
local Mob = require 'game.proto.mob'
local Item = require 'game.proto.item'

function Builder:init(scene)
    self.scene = scene
end


local function expand(type, map, out, x, y)
    local v = map:getCell(x, y)
    --if not v then
    --   v = type
    --   if not out:getCell(x, y) then out:setCell(x, y, type) end
    --end
    if not out:getCell(x, y) then out:setCell(x, y, type) end
    return v == type and 1 or 0
end

local function decimateCount(type, map, out, x, y)
    local v = map:getCell(x, y) or type
    return v == type and 1 or 0
end

local function fabricateCount(type, map, out, x, y)
    local v = map:getCell(x, y)
    return v == type and 1 or 0
end

local function withNeighbors(f, type, map, out, x, y)
    return
        f(type, map, out, x-1, y-1) +
        f(type, map, out, x-1, y) +
        f(type, map, out, x-1, y+1) +
        f(type, map, out, x+1, y-1) +
        f(type, map, out, x+1, y) +
        f(type, map, out, x+1, y+1) +
        f(type, map, out, x, y-1) +
        f(type, map, out, x, y+1)
end


function Builder:expand(type)
    local map = self.scene.feature
    local out = ROT.Type.Grid()
    for i, x, y, v in map:each() do
        out:setCell(x, y, v)
        withNeighbors(expand, type, map, out, x, y)
    end
    self.scene.feature = out
end


function Builder:erode(type)
    local map = self.scene.feature
    local out = ROT.Type.Grid()
    for i, x, y, v in map:each() do
        out:setCell(x, y, v)
        if v == type then
            local n = withNeighbors(fabricateCount, nil, map, out, x, y)
            if n > 0 then
                out:setCell(x, y, nil)
            end
        end
    end
    self.scene.feature = out
end

function Builder:decimate(type, neighbors, chance, newtype, cb, noExpand)
    local map = self.scene.feature
    local out = ROT.Type.Grid()
    self.scene.feature = out
    for i, x, y, v in map:each() do
        out:setCell(x, y, v)
        if v == type then
            local n = withNeighbors(decimateCount, type, map, out, x, y)
            if (n == neighbors) and RNG:random() <= chance then
                out:setCell(x, y, newtype)
                if not noExpand then
                    withNeighbors(expand, type, map, out, x, y)
                end
                if cb then cb(self.scene, x, y) end
            end
        end
    end
end


function Builder:fabricate(type, neighbors, chance, newtype, cb)
    local map = self.scene.feature
    local out = ROT.Type.Grid()
    self.scene.feature = out
    for i, x, y, v in map:each() do
        out:setCell(x, y, v)
        if v ~= type then
            local n = withNeighbors(fabricateCount, type, map, out, x, y)
            if (n == neighbors) and RNG:random() <= chance then
                out:setCell(x, y, newtype or type)
                if cb then cb(self.scene, x, y) end
            end
        end
    end
end


local Selection = ROT.Class:extend()

function Selection:init(scene, context, map)
    self.scene, self.context, self.map = scene, context, map
end

local function inTable(t, n)
    for i = 1, #t do
        if t[i] == n then return true end
    end
end

local function match(n, count)
    return n == count
        or (not count and n > 0)
        or (type(count) == 'number' and count < 0 and n ~= count * -1)
        or (type(count) == 'table' and inTable(count, n))
end

function Selection:near(type, count)
    local out = ROT.Type.Grid()
    for i, x, y, v in self.map:each() do
        local n = withNeighbors(fabricateCount, type, self.context, nil, x, y)
        if match(n, count) then
            out:setCell(x, y, v)
        end
    end
    self.map = out
    return self
end

function Selection:swap(type)
    local out = ROT.Type.Grid()
    for i, x, y, v in self.map:each() do
        out:setCell(x, y, type)
    end
    self.map = out
    return self
end

function Selection:exec(fn)
    for i, x, y, v in self.map:each() do
        fn(self.scene, x, y, v)
    end
    return self
end

function Selection:apply()
    for i, x, y, v in self.map:each() do
        self.context:setCell(x, y, v)
    end
    return self
end

function Builder:with(type)
    local map = self.scene.feature
    local out = ROT.Type.Grid()
    for i, x, y, v in map:each() do
        if v == type then
            out:setCell(x, y, v)
        end
    end
    return Selection(self.scene, map, out)
end

if false then
    self.builder:with('grass'):near('grass', 2):swap('statue'):apply()
end

return Builder
